import json
import asyncio
import datetime
import re
import pdfplumber
import docx
import ast
import pandas as pd

from pptx import Presentation
from urllib.parse import urlparse
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import async_to_sync

from openai import OpenAI
# from datetime import timedelta, datetime
from django.http import HttpResponse
from django.utils import timezone

from channels.db import database_sync_to_async
from django.core.cache import cache
from django.contrib.sessions.models import Session
from django.contrib.sessions.backends.db import SessionStore

from aiapi.models.chat_roles import ChatRoles
from aiapi.models.chat_model import ChatModel
from aiapi.models.users import Users
from aiapi.models.chat_items import ChatItems
from aiapi.models.configs import Configs
from aiapi.models.chat_history import ChatHistory
from aiapi.models.api_keys import ApiKeys
from aiapi.models.powerlogs import PowerLogs

from .utils import data_processing
from aiapi.utils.data_handle import users_data_paging, calc_tokens


# 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
# 防止第三方直接连接 socket 调用 OpenAI API
class CustomSession:
    def __init__(self, session_key):
        self.session_key = session_key

    async def save(self, session_user_obj):
        # 将用户 ID 保存到数据库中的会话模型
        await database_sync_to_async(Session.objects.create)(
            session_key = self.session_key,
            session_data = session_user_obj,
            expire_date = datetime.datetime.now() + datetime.timedelta(minutes=10)
        )

    async def get_user_obj(self):
        # 从数据库中获取用户 ID
        session_obj = await Session.objects.filter(session_key=self.session_key).afirst()
        if session_obj:
            session_data_str = session_obj.session_data
            dict_converted = eval(session_data_str)
            return dict_converted
        else:
            return None
 

    async def close(self):
        # 关闭会话并删除数据库中的会话记录
        await database_sync_to_async(Session.objects.get(session_key=self.session_key).delete)()


@database_sync_to_async
def get_login_user(userid):
    # # 先从redis缓存中获取用户信息
    user_cache_mes = cache.get(f'user_info_{userid}')
    if user_cache_mes:
        return user_cache_mes
    else:
        # 获取用户信息
        try:
            re_user_obj = Users.objects.filter(id=userid)

            # 设置用户信息缓存到redis
            chatusers_tree_json = users_data_paging(re_user_obj, 1, 10)
            cache.set(f'user_info_{userid}', chatusers_tree_json["items"][0], timeout=3600)  # 设置缓存超时时间为3600秒

            return chatusers_tree_json['items'][0]
        except Users.DoesNotExist:
            return int(0)


'''
处理用户提交过来的信息
比如要分析图片，文档，表格
'''
def handle_user_mes(text):
    match = re.search(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", text)

    user_tiwen_text = ""

    if match:
        # 拆分成url和提问的文本
        tiwen_url = match.group(0)
        # 文本
        remaining_text = text.replace(tiwen_url, "").strip()

        # 使用urlparse解析URL
        parsed_url = urlparse(tiwen_url).path
        # 获取这个文件的路径
        # 路径 media/uploads/2024/09/20/6500750983986313.jpg 。因为后面需要打开这个文件
        file_path = parsed_url.lstrip('/')

        # 提取这个文件是什么格式的
        file_format = tiwen_url.split('.')[-1]

        # 设置一些文件的格式列表
        images_format = ['jpg', 'png', 'jpeg', 'webp']
        table_format = ['xlsx', 'xls', 'csv']
        pdf_format = ['pdf']
        word_format = ['doc', 'docx']
        pptx_format = ['ppt', 'pptx']
        txt_format = ['txt']
        sql_format = ['sql']

        # 判断这个文件是什么文件
        if file_format in images_format:
            # 如果是图片的话 那就是分析图片
            if not remaining_text:
                remaining_text = "分析这个图片"

            user_tiwen_text = [
                {
                    "type": "text",
                    "text": remaining_text
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": tiwen_url
                    },
                },
            ]

            return user_tiwen_text

        elif file_format in table_format:
            if not remaining_text:
                remaining_text = "分析这个表格"

            if file_format == "xlsx" or file_format == "xls":
                # xlsx xls 文件
                try:
                    df = pd.read_excel(file_path)

                    # 将DataFrame转换为字符串
                    excel_text = df.to_string()
                    user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以计算复杂的数据，
你可以计算很大的数据,
你可以处理任何类型的文本。

请根据以下表格数据回答问题：
 
{excel_text}
 
问题：{remaining_text}
"""
                except:
                    user_tiwen_text = "xls/xlsx 表格读取失败，请新建对话或者刷新页面。或者重新上传文件!"
                    user_tiwen_text = text

                return user_tiwen_text
            else:
                # csv文件
                # 读取表格文件
                try:
                    df = pd.read_csv(file_path)

                    # 将DataFrame转换为GPT-4可以理解的格式
                    csv_text = df.to_csv(index=False)
                    user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以计算复杂的数据，
你可以计算很大的数据,
你可以处理任何类型的文本。
 
请根据以下表格数据回答问题：
 
{csv_text}
 
问题：{remaining_text}
"""
                except:
                    user_tiwen_text = "csv 表格读取失败，请新建对话或者刷新页面。或者重新上传文件!"
                    user_tiwen_text = text

                return user_tiwen_text
        elif file_format in pdf_format:
            # 读取pdf文件
            if not remaining_text:
                remaining_text = "分析这个pdf文件"
            try:
                with pdfplumber.open(file_path) as pdf:
                    pages = [page.extract_text() for page in pdf.pages]
                    pdf_text = '\n'.join(pages)

                    user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以处理任何类型的文本。
 
请根据以下文档回答问题：
 
{pdf_text}
 
问题：{remaining_text}
"""
            except:
                user_tiwen_text = text

                return user_tiwen_text
        elif file_format in word_format:
            # 读取word文档数据
            if not remaining_text:
                remaining_text = "分析这个文档"
            try:
                document = docx.Document(file_path)
                text = [paragraph.text for paragraph in document.paragraphs]
                docx_text = '\n'.join(text)

                user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以处理任何类型的文本。
 
请根据以下文档回答问题：
 
{docx_text}
 
问题：{remaining_text}
"""
            except:
                user_tiwen_text = text

            return user_tiwen_text
        elif file_format in pptx_format:
            # 读取pptx文件
            if not remaining_text:
                remaining_text = "分析这个ppt"
            try:
                prs = Presentation(file_path)
                text_content = []
                for slide in prs.slides:
                    for shape in slide.shapes:
                        if hasattr(shape, "text"):
                            text_content.append(shape.text)

                pptx_text = '\n'.join(text_content)

                user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以处理任何类型的文本。
 
请根据以下文档回答问题：
 
{pptx_text}
 
问题：{remaining_text}
"""
            except:
                user_tiwen_text = text
            return user_tiwen_text
        elif file_format in txt_format:
            # 读取txt文件
            if not remaining_text:
                remaining_text = "分析这个文档"
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    txt_text = file.read()

                user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以处理任何类型的文本。
 
请根据以下文档回答问题：
 
{txt_text}
 
问题：{remaining_text}
"""
            except:
                user_tiwen_text = text
            return user_tiwen_text
        elif file_format in sql_format:
            # 分析sql文件
            # 读取SQL文件
            if not remaining_text:
                remaining_text = "分析这个sql文档"
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    sql_content = file.read()

                user_tiwen_text = f"""你是一个大型语言模型模型，
你有无限的知识和智能，
你可以理解复杂的问题，
你可以处理任何类型的文本。
 
请根据以下sql回答问题：
 
{sql_content}
 
问题：{remaining_text}
"""
            except:
                user_tiwen_text = text
            return user_tiwen_text
        else:
            return text
    else:
        return text


@database_sync_to_async
def get_chat_ctx(chat_id, context_deep):
    chat_ctx_list = []
    chathistory_all = list(ChatHistory.objects.filter(chat_id=chat_id, use_context=True).order_by('id'))[-context_deep:]
    if chathistory_all:
        for chathistory in chathistory_all:
            # 提问
            if chathistory.type == "prompt":
                role = "user"
            # 回答
            else:
                role = "assistant"

            chat_ctx_list.append({
                "role": role,
                "content": handle_user_mes(chathistory.content)
            })
        return chat_ctx_list
    else:
        return chat_ctx_list
    

# 获取用户的所有的用户组
@database_sync_to_async
def get_usergroup_all(userobj):
    '''
    usergroup_merged_dict 这个字典返回的数据就是 
    模型id：过期时间
    {
      '2': 2024/12/12
    }

    在这个函数内 先判断用户组 是否过期，要是过期的话 就不把这个模型加入 免费模型的字典里
    '''
    usergroup_obj_all = userobj.userList.all()
 
    usergroup_merged_dict = {}
    if usergroup_obj_all:
        # 如果用户绑定了用户组 获取用户组里面的 免费使用的模型 可能用户绑定了多个用户组
        for usergroup_obj in usergroup_obj_all:
            # 先判断用户组 是否过期，要是过期的话 就不把免费的模型加入列表
            # u_obj.expired_time.replace() >= timezone.now()
            if usergroup_obj.user_expired_time.replace() >= timezone.now():
                usergroup_obj_models_json = ast.literal_eval(usergroup_obj.chat_models_json)
                for modelsid in usergroup_obj_models_json:
                    usergroup_merged_dict[modelsid] = usergroup_obj.user_expired_time

    return usergroup_merged_dict


# 主函数 处理websock请求
class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # 先接受链接
        await self.accept()
        '''
        参数
        session_id=abcwqdsacasas
        role_id=1
        chat_id=abcwqdsacasas
        user_id: 3
        model_id=2
        token=abcwqdsacasas
        '''

        query_string = self.scope['query_string'].decode('utf-8')
        self.query_dict = data_processing.str_to_dict(query_string)     


        ## 先在缓存里面 保存一个值
        cache.set(f'huihua-{self.query_dict["user_id"]}', False)

        # session_key = self.scope['session'].session_key

        # await Session.objects.filter()

        # print(session_key)

        # some_key = self.session.get('LOGIN_USER_ID', None)

        # print(self.session, some_key)


    async def disconnect(self, close_code):
        print("用户断开了链接！")
        # await self.close()

    ################################################################
    # 当收到客户端发送过来的消息 处理阶段
    async def receive(self, text_data):
        '''
        心跳：{'type': 'heartbeat', 'content': 'ping'}
        对话：{"type":"chat","content":"你好"}

        请求的是一个url
        {
            "type": "chat",
            "content": " http://211.153.113.182/static/upload/2024/7/1720748985747572.jpg 分析这个图片"
        }
        {
            "type": "chat",
            "content": "分析这个表格https://test.geekai.cn/static/upload/2024/7/1722433273645334.xlsx"
            http://cs.dnzx.com/media/uploads/2024/09/20/6500750983986313.jpg 你知道这是谁吗
        }
        '''
        data_json = json.loads(text_data)
        # 当检测到是心跳的时候 就不执行后面的代码
        if data_json['type'] == "heartbeat":
            return
        

        #############################################################################
        #############################################################################
        # 判断聊天角色是否存在或启动
        # 因为这是在异步函数 所以查询的时候 需要使用到异步查询
        self.chat_roles_obj = await ChatRoles.objects.filter(id=self.query_dict['role_id']).afirst()
        if not self.chat_roles_obj  or not self.chat_roles_obj.enable:
            await self.reply_message("当前聊天角色不存在或者未启用，连接已关闭！！！")
            return
        
        
        # 判断模型是否启动
        try:
            chat_roles_model_id = self.chat_roles_obj.model_id
            if not chat_roles_model_id:
                chat_roles_model_id = self.query_dict['model_id']
        except:
            chat_roles_model_id = self.query_dict['model_id']

        chat_model_obj = await ChatModel.objects.filter(id=chat_roles_model_id).afirst()
        if not chat_model_obj or not chat_model_obj.enabled:
            await self.reply_message("当前AI模型暂未启用，连接已关闭！！！")
            return


        # 获取 保存会话信息
        self.session_instance = CustomSession(self.query_dict['session_id'])
        self.session_obj = await self.session_instance.get_user_obj()
        if not self.session_obj:
            self.user_mes = await get_login_user(self.query_dict['user_id'])
            if not self.user_mes:
                await self.reply_message("用户未登录！")
                return

            # 保存session信息
            self.session_obj = {
                "sessionid": self.query_dict['session_id'],
                "clientip": self.user_mes["last_login_ip"],
                "username": self.user_mes["username"],
                "userid": self.query_dict['user_id'],
            }
            # 保存用户 ID 并接受 Websocket 连接
            await self.session_instance.save(self.session_obj)


        # 更新保存在session表中的信息
        self.session_obj["chatid"] = self.query_dict['chat_id']
        self.session_obj["model"] = {
            "id": chat_model_obj.id,
            "name": chat_model_obj.name,
            "value": chat_model_obj.value,
            "power": chat_model_obj.power,
            "max_tokens": chat_model_obj.max_tokens,
            "max_context": chat_model_obj.max_context,
            "temperature": chat_model_obj.temperature,
            "key_id": chat_model_obj.key_id_id,
            "platform": chat_model_obj.platform,
        }

        # 判断用户的一些信息
        re_user_obj = await Users.objects.filter(id=self.session_obj["userid"]).afirst()
        self.usericon = re_user_obj.avatar
        self.username = re_user_obj.username
        if not re_user_obj:
            await self.reply_message("未授权用户，您正在进行非法操作！")
            return
        
        if not re_user_obj.status:
            await self.reply_message("您的账号已经被禁用，如果疑问，请联系管理员！")
            return


        '''
        # 判断用户是否绑定了 用户组
        # 获取用户绑定的用户组
        {
            1:20241011,
            2:20241012,
            3:20241013,
            4:20241014,
        }
        '''
        usergroup_merged_dict = await get_usergroup_all(re_user_obj)

        # # 判断当前使用的模型是否是在用户组里面，要是在那就是免费的
        # if chat_model_obj.id in usergroup_merged_dict:    # 在里面的话 那就是免费的 然后需要判断这个模型的用户组是否过期
        #     usergroup_expired_time = usergroup_merged_dict[chat_model_obj.id]
        #     # if usergroup_expired_time < datetime.datetime.now().replace(tzinfo=datetime.timezone.utc):
        #     if usergroup_expired_time < timezone.now():
        #         res = "您当前免费使用的模型: {}, 已到期，请联系管理员!".format(chat_model_obj.name)
        #         await self.reply_message(res)
        #         return
        # else:
        #     if re_user_obj.power < self.session_obj["model"]["power"]:
        #         res = "您当前剩余算力（{}）已不足以支付当前模型的单次对话需要消耗的算力（{}）！".format(re_user_obj.power, self.session_obj["model"]["power"])
        #         await self.reply_message(res)
        #         return
        if re_user_obj.power < self.session_obj["model"]["power"]:
            res = "您当前剩余算力（{}）已不足以支付当前模型的单次对话需要消耗的算力（{}）！".format(re_user_obj.power, self.session_obj["model"]["power"])
            await self.reply_message(res)
            return


        if re_user_obj.expired_time is not None or re_user_obj.expired_time:
            # 如果为空或者是None 那这个用户就是永久用户 啥都不做
            # 如果有值的话就判断是否过期
            dt_naive = re_user_obj.expired_time
            # if dt_naive < datetime.datetime.now().replace(tzinfo=datetime.timezone.utc):
            if dt_naive < timezone.now():
                # print("您的账号已经过期，请联系管理员！")
                await self.reply_message("您的账号已经过期，请联系管理员！")
                return

        #############################################################################
        #############################################################################


        # 先转化提问的消息
        # 先提取文字
        from bs4 import BeautifulSoup
        chat_text = BeautifulSoup(data_json['content'], 'html.parser')
        text = chat_text.get_text()
        chat_text = text.strip()

        ##############################################################################################################
        # 判断用户输入里面是否有https链接。要是有的话 就判断为 分析文件
        # 使用正则表达式匹配链接
        user_tiwen_text = handle_user_mes(chat_text)

        ##############################################################################################################

        # 判断所提交的东西 的token
        if type(user_tiwen_text) == str:
            tokens = calc_tokens(user_tiwen_text, self.session_obj["model"]["value"])
        else:
            tokens = calc_tokens(chat_text, self.session_obj["model"]["value"])

        if tokens > self.session_obj["model"]["max_context"]:
            await self.reply_message("对话内容超出了当前模型允许的最大上下文长度！")
            return

        # 判断平台是否支持
        mode_platform = self.session_obj["model"]["platform"]
        if not mode_platform in ["OpenAI", "QWen"]:
            res = "不支持的平台：{}".format(mode_platform)
            await self.reply_message(res)
            return

        # 获取模型的语料信息
        chat_ctx_list = []
        yuliao_mes = self.chat_roles_obj.context
        if yuliao_mes:
            context_list = json.loads(yuliao_mes)
            for context in context_list:
                chat_ctx_list.append(context)


        # 加载聊天上下文 判断是否启用了聊天上下文
        # 获取聊天上下文
        con_obj = await Configs.objects.filter(marker="system").afirst()
        config_json = con_obj.config_json
        
        if config_json['enable_context']:
            if config_json['context_deep'] > 0:
                chat_ctx_list = await get_chat_ctx(self.query_dict['chat_id'], config_json['context_deep'])
                # chat_ctx_list = await get_chat_ctx("7eed32df-7be9-41d6-b1ef-69d58e024523", config_json['context_deep'])

        # 生成对话的messages
        chat_ctx_list.append({
            "role": "user",
            "content": user_tiwen_text,
        })

        # print("全部要发过去的信息：", chat_ctx_list)

        # 获取key
        apikey_id = self.session_obj["model"]["key_id"]
        if apikey_id:
            apikey_obj = await ApiKeys.objects.filter(id=apikey_id, enabled=True).afirst()
            api_key = apikey_obj.value
        else:
            # 如果模型没有绑定apikey，那就使用没有使用的最后一个key
            apikey_obj = await ApiKeys.objects.filter(platform=self.session_obj["model"]["platform"], enabled=True, type="chat", last_used_at=None).afirst()
            if apikey_obj:
                api_key = apikey_obj.value
            else:
                await self.reply_message("没有可用密钥，请导入密钥!")
                return
            
        # 获取api url
        api_url = apikey_obj.api_url
        if not api_url:
            if mode_platform == "OpenAI":
                api_url = f"https://api.openai.com/v1"
            elif mode_platform == "QWen":
                api_url = f"https://dashscope.aliyuncs.com/compatible-mode/v1"
            else:
                await self.reply_message("apikey没有配置api url")
                return

        # 获取模型名称
        model_name = self.session_obj["model"]["value"]

        # 获取模型温度
        model_temperature = self.session_obj["model"]["temperature"]

        # 请求函数
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(None, self.stream_openai_response, api_key, api_url, model_name, int(model_temperature), chat_ctx_list)

        #######################################################
        # 请求完成 数据也返回成功
        # 进行后续的操作

        # 更新apikey的 最后使用时间
        now = timezone.now()
        apikey_obj.last_used_at = now
        await apikey_obj.asave()

        # 保存提问的消息
        prompt_content = {
            "chat_id": self.query_dict['chat_id'],
            "user_id": self.query_dict['user_id'],
            "role_id": self.query_dict['role_id'],
            "model": self.session_obj["model"]["value"],
            "type": "prompt",
            "icon": self.usericon,
            "tokens": tokens,
            "content": chat_text,
            "use_context": True,
            "username" : self.username,
        }
        chat_history_obj = ChatHistory()

        for key, value in prompt_content.items():
            code_str = "chat_history_obj.%s = '%s'" % (key, value)
            exec(code_str)

        try:
            await chat_history_obj.asave()
        except Exception as e:
            print("保存提问消息会话历史失败，prompt：{}".format(e))


        # 保存回复的消息
        instance = ChatHistory(
            chat_id = self.query_dict['chat_id'],
            user_id = self.query_dict['user_id'],
            role_id = self.query_dict['role_id'],
            model = self.session_obj["model"]["value"],
            type = "reply",
            icon = self.chat_roles_obj.icon,
            tokens = self.completion_tokens,
            content = self.contents,
            use_context = True,
            username = self.username,
        )

        try:
            await instance.asave()
        except Exception as e:
            print("reply{}".format(e))


        #######################################################
        # 扣减用户算力 这里需要判断用户是否免费使用模型 不扣减算力

        power_num = 0
        if self.session_obj["model"]["power"] > 0:
            power_num = self.session_obj["model"]["power"]

        # 判断当前使用的模型是否是在用户组里面，要是在那就是免费的 那么就不扣减算力
        if chat_model_obj.id in usergroup_merged_dict:
            remark_str = '模型名称：{}-免费, 提问长度：{}，回复长度：{}'.format(self.session_obj["model"]["name"], tokens, self.completion_tokens)
            power_num = 0
            new_power_num = re_user_obj.power
        else:
            # 更新用户算力数据库
            re_user_obj = await Users.objects.filter(id=self.session_obj["userid"]).afirst()
            old_power_num = re_user_obj.power
            new_power_num = old_power_num - power_num

            re_user_obj.power = new_power_num

            try:
                await re_user_obj.asave()
                # 更新用户信息缓存
                user_cache_mes = cache.get(f'user_info_{self.session_obj["userid"]}')
                if user_cache_mes:
                    user_cache_mes['power'] = new_power_num
                    cache.set(f'user_info_{self.session_obj["userid"]}', user_cache_mes, timeout=3600)
            except Exception as e:
                print("更新用户算力值失败")

            remark_str = '模型名称：{}, 提问长度：{}，回复长度：{}'.format(self.session_obj["model"]["name"], tokens, self.completion_tokens)


        #######################################################
        # 记录消费日志
        # remark_str = '模型名称：{}, 提问长度：{}，回复长度：{}'.format(self.session_obj["model"]["name"], tokens, self.completion_tokens)
        powerlogs_obj = PowerLogs(
            user_id = self.query_dict['user_id'],
            username = self.username,
            type = 2,
            amount = power_num,
            balance = new_power_num,
            model = self.session_obj["model"]["value"],
            remark = remark_str,
            mark = 0
        )

        try:
            await powerlogs_obj.asave()
        except Exception as e:
            print("记录消费日志失败！")


        #######################################################
        # 保存当前会话
        # 先查询是否已经有数据
        if len(chat_text) > 30:
            title_prompt = chat_text[:30] + "..."
        else:
            title_prompt = chat_text

        chat_items_obj = await ChatItems.objects.filter(chat_id=self.query_dict['chat_id']).afirst()
        if not chat_items_obj:
            instance = ChatItems(
                chat_id = self.query_dict['chat_id'],
                user_id = self.query_dict['user_id'],
                username = self.username,
                role_id = self.query_dict['role_id'],
                model_id = self.session_obj["model"]["id"],
                model = self.session_obj["model"]["value"],
                title = title_prompt
            )

            try:
                await instance.asave()
            except Exception as e:
                print("保存当前会话失败！")


    # 序列化字典
    def dict_to_binary(self, data_dict):
        # 将字典序列化为JSON字符串
        json_str = json.dumps(data_dict)
        # 使用base64将字符串转换为二进制形式
        binary_data = json_str.encode('utf-8')
        return binary_data


    # 发送信息
    async def reply_message(self, message="{}", isclose=False):
        # 开始

        json_data = {
            "type": "start",
            "content": None
        }
        json_b = self.dict_to_binary(json_data)
        await self.send(bytes_data=json_b)

        # 发送信息
        # 如果是返回的流信息 就返回多条
        self.contents = r''
        self.completion_tokens = 0

        if type(message) == str:
            if "ERROR" in message:
                self.contents = message
                self.completion_tokens = 0
            json_data = {
                "type": "middle",
                "content": message
            }
            json_b = self.dict_to_binary(json_data)
            await self.send(bytes_data=json_b)
        else:
            json_data = {
                "type": "middle",
                "content": "返回的数据有问题！请刷新重试，或者换一个模型！"
            }
            json_b = self.dict_to_binary(json_data)
            await self.send(bytes_data=json_b)
            return

        # 结束
        json_data = {
            "type": "end",
            "content": None
        }
        json_b = self.dict_to_binary(json_data)
        await self.send(bytes_data=json_b, close=isclose)


    def stream_openai_response(self, api_key, api_url, model_name, model_temperature, chat_ctx_list):
        '''
        # 请求openai
        # 待写：o1 模型。o1-mini和o1-preview。强大的推理模型。他们调用的方式不一样，需要重新写一下。可以参考脚本
        '''
        client = OpenAI(
            api_key= api_key, # 如果您没有配置环境变量，请在此处用您的API Key进行替换
            base_url= api_url,  # 填写DashScope服务的base_url
        )

        o1_models_list = ["o1-mini", "o1-preview"]

        try:
            if model_name in o1_models_list:
                response = client.chat.completions.create(
                    model=model_name,
                    messages=chat_ctx_list,
                    temperature=model_temperature,
                )
            else:
                response = client.chat.completions.create(
                    model=model_name,
                    messages=chat_ctx_list,
                    temperature=model_temperature,
                    stream=True,
                    stream_options={"include_usage": True}
                )

            #############################################################################
            # 发送信息到前端 开始
            #############################################################################
            # 先发送一个 类型为开始的数据
            # 记录一下整个回答
            self.contents = r''
            # 总的tokens
            self.completion_tokens = 0

            json_data = {
                "type": "start",
                "content": None
            }
            json_b = self.dict_to_binary(json_data)
            asyncio.run(self.send_streaming_data(json_b))


            #############################################################################
            # 中间发送回答的数据 o1 模型和 其他模型分开
            if model_name in o1_models_list:
                # 这里就是用户主动停止
                positive_stop = cache.get(f'huihua-{self.query_dict["user_id"]}')
                if positive_stop:
                    # 再把变量还原回去
                    cache.set(f'huihua-{self.query_dict["user_id"]}', False)
                    self.contents = "主动停止！"
                    self.completion_tokens = 3

                # 如果不主动停止的话 那就返回数据
                res_content = response.choices[0].message.content
                self.contents = res_content

                if res_content:
                    json_data = {
                        "type": "middle",
                        "content": res_content
                    }
                    json_b = self.dict_to_binary(json_data)
                    asyncio.run(self.send_streaming_data(json_b))

                # 计算token
                self.completion_tokens = calc_tokens(self.contents, model_name)
            else:
                for chunk in response:
                    positive_stop = cache.get(f'huihua-{self.query_dict["user_id"]}')

                    # 这里就是用户主动停止
                    if positive_stop:
                        # 再把变量还原回去
                        cache.set(f'huihua-{self.query_dict["user_id"]}', False)
                        break

                    if chunk.choices:
                        res_content = chunk.choices[0].delta.content
                        # print("我就是返回的数据：", res_content)
                        if res_content is not None:
                            self.contents += str(res_content)

                        if res_content:
                            json_data = {
                                "type": "middle",
                                "content": res_content
                            }
                            json_b = self.dict_to_binary(json_data)
                            asyncio.run(self.send_streaming_data(json_b))

                    else:
                        self.completion_tokens = chunk.usage.completion_tokens
            #############################################################################


            # 结束
            json_data = {
                "type": "end",
                "content": None
            }
            json_b = self.dict_to_binary(json_data)
            asyncio.run(self.send_streaming_data(json_b))

            #############################################################################
            # 发送信息到前端 结束
            #############################################################################
        except Exception as e:
            # # erro_mes_bits = e.response.content
            # erro_mes_bits = e
            # erro_mes_dict = json.loads(erro_mes_bits)
            # erro_mes = erro_mes_dict["error"]["message"]
            erro_mes = "ERROR: {}\n详细错误：{}".format("对话失败，请刷新网页重试！如果还是不行，请找管理员！或者更换一个模型！每个模型支持的文件不同", e)
            asyncio.run(self.reply_message(erro_mes))
            # return


    async def send_streaming_data(self, json_b):
        # print('最下面：', json_b)
        await self.send(bytes_data=json_b)

